﻿using System;
using System.Collections.Generic;
using System.Linq;
using Xenta.Attributes;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Operations;
using Xenta.Utils;

namespace Xenta.Plugins
{
    [Dependency("eded1853-9e43-433c-9a4e-0c454c32f984", 
        Name = "Simple Sales Model")]
    public sealed class SimpleSalesModel : ISalesModel
    {
        public bool CanSale(SalesItemEntity item)
        {
            return item.Attribute(Par.In("Plugin").Of(this).Name("Enabled")).ParseAs(true) &&
                item.Seller.Account.Attribute(Par.In("Plugin").Of(this).Name("Enabled")).ParseAs(true);    
        }

        public decimal CalcHandlingFee(OrderEntity order)
        {
            return Decimal.Zero;
        }

        public void OnOrderStatusChanged(IUnitOfWork uow, OrderEntity order)
        {
            if(order.Flags.HasFlag(OrderFlags.Confirmed))
            {
                uow.Execute(new IssueInvoice
                {
                    EntityID = uow.Execute(new PlaceInvoice
                    {
                        OrderID = order.EntityID,
                        Total = order.Total,
                        IssuedOn = DateTime.UtcNow
                    }).Result.EntityID
                });
            }
            else if(order.Flags.HasFlag(OrderFlags.Processing) ||
                order.Flags.HasFlag(OrderFlags.Canceled))
            {
                string shipComment = String.Empty;
                IDictionary<String, String> shipAttributes = null;
                using(var ioc = uow.Infrastructure.DependencyResolver())
                {
                    var shippingMethod = ioc.Resolve(order.ShippingMethodID)
                        as IShippingMethod;
                    shipAttributes = order.Flags.HasFlag(OrderFlags.Processing)
                        ? shippingMethod.InitShipping(order, out shipComment)
                        : shippingMethod.CancelShipping(order, out shipComment);
                }
                if(!String.IsNullOrWhiteSpace(shipComment))
                {
                    uow.Execute(new CreateOrderComment
                    {
                        HolderID = order.EntityID,
                        Text = shipComment,
                        Flags = OrderCommentFlags.Active | OrderCommentFlags.Public
                    });
                }
                if(shipAttributes != null && shipAttributes.Any())
                {
                    uow.Execute(new BulkUpdateOrderAttributes
                    {
                        HolderID = order.EntityID,
                        Attributes = shipAttributes
                    });
                    uow.Commit();
                }
                if(order.Flags.HasFlag(OrderFlags.Canceled))
                {
                    foreach(var i in order.Invoices
                        .Where(x => 
                            x.Flags.HasFlag(InvoiceFlags.Pending) || 
                            x.Flags.HasFlag(InvoiceFlags.Issued))
                        .ToList())
                    {
                        uow.Execute(new CancelInvoice
                        {
                            EntityID = i.EntityID
                        });
                    }
                }
            }
        }

        public void OnPaymentStatusChanged(IUnitOfWork uow, PaymentEntity payment)
        {
            if(!payment.Flags.HasFlag(PaymentFlags.Refunded) &&
                !payment.Flags.HasFlag(PaymentFlags.Captured))
            {
                return;
            }

            var invoice = payment.Invoice;
            decimal paidAmount = invoice.PaidAmount;
            decimal refundAmount = invoice.RefundAmount;
            if(payment.Flags.HasFlag(PaymentFlags.Refunded))
                refundAmount += payment.Amount;
            else
                paidAmount += payment.Amount;

            invoice = uow.Execute(new UpdateInvoice
            {
                EntityID = invoice.EntityID,
                Total = invoice.Total,
                PaidAmount = paidAmount,
                RefundAmount = refundAmount,
                Flags = invoice.Flags,
                IssuedOn = invoice.IssuedOn
            }).Result;
            if(invoice.PaidAmount < invoice.Total) 
                return;
            uow.Execute(new CompleteInvoice
            {
                EntityID = invoice.EntityID
            });
        }

        public void OnInvoiceStatusChanged(IUnitOfWork uow, InvoiceEntity invoice)
        {
            if(!invoice.Flags.HasFlag(InvoiceFlags.Paid))
                return;
            uow.Execute(new ProcessOrder
            {
                EntityID = invoice.Order.EntityID
            });
        }
    }

    [Dependency("d35cf83b-6790-4752-b123-0bf60f1d7b12",
        Name = "Deferred Payment Sales Model")]
    public sealed class DeferredPaymentSalesModel : ISalesModel
    {
        public bool CanSale(SalesItemEntity item)
        {
            return item.Attribute(Par.In("Plugin").Of(this).Name("Enabled")).ParseAs(true) &&
                item.Seller.Account.Attribute(Par.In("Plugin").Of(this).Name("Enabled")).ParseAs(true);
        }

        public decimal CalcHandlingFee(OrderEntity order)
        {
            var fee = order.Seller.Account.Attribute(Par
                .In("Plugin").Of(this).Name("Fee"))
                .ParseAs(Decimal.Zero);
            if(fee != Decimal.Zero)
            {
                bool isPercent = order.Seller.Account.Attribute(Par
                    .In("Plugin").Of(this).Name("IsPercent"))
                    .ParseAs(true);
                if(isPercent)
                    fee = order.Total / 100 * fee;
            }
            return fee;
        }

        public void OnOrderStatusChanged(IUnitOfWork uow, OrderEntity order)
        {
            if(order.Flags.HasFlag(OrderFlags.Confirmed))
            {
                //mark order for processing
                uow.Execute(new ProcessOrder
                {
                    EntityID = order.EntityID
                });
                //init shipping
                string shipComment = String.Empty;
                IDictionary<String, String> shipAttributes = null;
                using(var ioc = uow.Infrastructure.DependencyResolver())
                {
                    shipAttributes = (ioc.Resolve(order.ShippingMethodID)as IShippingMethod)
                        .InitShipping(order, out shipComment);
                }
                if(!String.IsNullOrWhiteSpace(shipComment))
                {
                    uow.Execute(new CreateOrderComment
                    {
                        HolderID = order.EntityID,
                        Text = shipComment,
                        Flags = OrderCommentFlags.Active | OrderCommentFlags.Public
                    });
                }
                if(shipAttributes != null && shipAttributes.Any())
                {
                    uow.Execute(new BulkUpdateOrderAttributes
                    {
                        HolderID = order.EntityID,
                        Attributes = shipAttributes
                    });
                    uow.Commit();
                }
                //issue invoice
                uow.Execute(new IssueInvoice
                {
                    EntityID = uow.Execute(new PlaceInvoice
                    {
                        OrderID = order.EntityID,
                        Total = order.Total,
                        IssuedOn = DateTime.UtcNow
                    }).Result.EntityID
                });
            }
            else if(order.Flags.HasFlag(OrderFlags.Canceled))
            {
                //cancel shipping
                string shipComment = String.Empty;
                IDictionary<String, String> shipAttributes = null;
                using(var ioc = uow.Infrastructure.DependencyResolver())
                {
                    shipAttributes = (ioc.Resolve(order.ShippingMethodID) as IShippingMethod)
                        .CancelShipping(order, out shipComment);
                }
                if(!String.IsNullOrWhiteSpace(shipComment))
                {
                    uow.Execute(new CreateOrderComment
                    {
                        HolderID = order.EntityID,
                        Text = shipComment,
                        Flags = OrderCommentFlags.Active | OrderCommentFlags.Public
                    });
                }
                if(shipAttributes != null && shipAttributes.Any())
                {
                    uow.Execute(new BulkUpdateOrderAttributes
                    {
                        HolderID = order.EntityID,
                        Attributes = shipAttributes
                    });
                    uow.Commit();
                }
                //cancel invoices
                foreach(var i in order.Invoices
                    .Where(x =>
                        x.Flags.HasFlag(InvoiceFlags.Pending) ||
                        x.Flags.HasFlag(InvoiceFlags.Issued))
                    .ToList())
                {
                    uow.Execute(new CancelInvoice
                    {
                        EntityID = i.EntityID
                    });
                }
            }
        }

        public void OnPaymentStatusChanged(IUnitOfWork uow, PaymentEntity payment)
        {
            if(!payment.Flags.HasFlag(PaymentFlags.Refunded) &&
                !payment.Flags.HasFlag(PaymentFlags.Captured))
            {
                return;
            }

            var invoice = payment.Invoice;
            decimal paidAmount = invoice.PaidAmount;
            decimal refundAmount = invoice.RefundAmount;
            if(payment.Flags.HasFlag(PaymentFlags.Refunded))
                refundAmount += payment.Amount;
            else
                paidAmount += payment.Amount;

            invoice = uow.Execute(new UpdateInvoice
            {
                EntityID = invoice.EntityID,
                Total = invoice.Total,
                PaidAmount = paidAmount,
                RefundAmount = refundAmount,
                Flags = invoice.Flags,
                IssuedOn = invoice.IssuedOn
            }).Result;
            if(invoice.PaidAmount < invoice.Total)
                return;
            uow.Execute(new CompleteInvoice
            {
                EntityID = invoice.EntityID
            });
        }

        public void OnInvoiceStatusChanged(IUnitOfWork uow, InvoiceEntity invoice)
        {
            if(!invoice.Flags.HasFlag(InvoiceFlags.Paid))
                return;
            uow.Execute(new CompleteOrder
            {
                EntityID = invoice.Order.EntityID
            });
        }
    }

    [Dependency("742ec341-6832-47e1-9120-64ec9533de92",
        Name = "Partial Payment Sales Model")]
    public sealed class PartialPaymentSalesModel : ISalesModel
    {
        public bool CanSale(SalesItemEntity item)
        {
            return item.Attribute(Par.In("Plugin").Of(this).Name("Enabled")).ParseAs(true) &&
                item.Seller.Account.Attribute(Par.In("Plugin").Of(this).Name("Enabled")).ParseAs(true);
        }

        public decimal CalcHandlingFee(OrderEntity order)
        {
            var fee = order.Seller.Account.Attribute(Par
                .In("Plugin").Of(this).Name("Fee"))
                .ParseAs(Decimal.Zero);
            if(fee != Decimal.Zero)
            {
                bool isPercent = order.Seller.Account.Attribute(Par
                    .In("Plugin").Of(this).Name("IsPercent"))
                    .ParseAs(true);
                if(isPercent)
                    fee = order.Total / 100 * fee;
            }
            return fee;
        }

        public void OnOrderStatusChanged(IUnitOfWork uow, OrderEntity order)
        {
            if(order.Flags.HasFlag(OrderFlags.Confirmed))
            {
                var installment = order.Seller.Account.Attribute(Par
                    .In("Plugin").Of(this).Name("Installment"))
                    .ParseAs(50);
                //init shipping
                string shipComment = String.Empty;
                IDictionary<String, String> shipAttributes = null;
                using(var ioc = uow.Infrastructure.DependencyResolver())
                {
                    shipAttributes = (ioc.Resolve(order.ShippingMethodID) as IShippingMethod)
                        .InitShipping(order, out shipComment);
                }
                if(!String.IsNullOrWhiteSpace(shipComment))
                {
                    uow.Execute(new CreateOrderComment
                    {
                        HolderID = order.EntityID,
                        Text = shipComment,
                        Flags = OrderCommentFlags.Active | OrderCommentFlags.Public
                    });
                }
                if(shipAttributes != null && shipAttributes.Any())
                {
                    uow.Execute(new BulkUpdateOrderAttributes
                    {
                        HolderID = order.EntityID,
                        Attributes = shipAttributes
                    });
                    uow.Commit();
                }
                //issue the first invoice
                uow.Execute(new IssueInvoice
                {
                    EntityID = uow.Execute(new PlaceInvoice
                    {
                        OrderID = order.EntityID,
                        Total = order.Total / 100 * installment,
                        IssuedOn = DateTime.UtcNow
                    }).Result.EntityID
                });
            }
            else if(order.Flags.HasFlag(OrderFlags.Processing))
            {
                var installment = order.Seller.Account.Attribute(Par
                    .In("Plugin").Of(this).Name("Installment"))
                    .ParseAs(50);
                //issue the second invoice
                uow.Execute(new IssueInvoice
                {
                    EntityID = uow.Execute(new PlaceInvoice
                    {
                        OrderID = order.EntityID,
                        Total = order.Total / 100 * (100 - installment),
                        IssuedOn = DateTime.UtcNow
                    }).Result.EntityID
                });
            }
            else if(order.Flags.HasFlag(OrderFlags.Canceled))
            {
                //cancel shipping
                string shipComment = String.Empty;
                IDictionary<String, String> shipAttributes = null;
                using(var ioc = uow.Infrastructure.DependencyResolver())
                {
                    shipAttributes = (ioc.Resolve(order.ShippingMethodID) as IShippingMethod)
                        .CancelShipping(order, out shipComment);
                }
                if(!String.IsNullOrWhiteSpace(shipComment))
                {
                    uow.Execute(new CreateOrderComment
                    {
                        HolderID = order.EntityID,
                        Text = shipComment,
                        Flags = OrderCommentFlags.Active | OrderCommentFlags.Public
                    });
                }
                if(shipAttributes != null && shipAttributes.Any())
                {
                    uow.Execute(new BulkUpdateOrderAttributes
                    {
                        HolderID = order.EntityID,
                        Attributes = shipAttributes
                    });
                    uow.Commit();
                }
                //cancel invoices
                foreach(var i in order.Invoices
                        .Where(x =>
                            x.Flags.HasFlag(InvoiceFlags.Pending) ||
                                x.Flags.HasFlag(InvoiceFlags.Issued))
                        .ToList())
                {
                    uow.Execute(new CancelInvoice
                    {
                        EntityID = i.EntityID
                    });
                }
            }
        }

        public void OnPaymentStatusChanged(IUnitOfWork uow, PaymentEntity payment)
        {
            if(!payment.Flags.HasFlag(PaymentFlags.Refunded) &&
                !payment.Flags.HasFlag(PaymentFlags.Captured))
            {
                return;
            }

            var invoice = payment.Invoice;
            decimal paidAmount = invoice.PaidAmount;
            decimal refundAmount = invoice.RefundAmount;
            if(payment.Flags.HasFlag(PaymentFlags.Refunded))
                refundAmount += payment.Amount;
            else
                paidAmount += payment.Amount;

            invoice = uow.Execute(new UpdateInvoice
            {
                EntityID = invoice.EntityID,
                Total = invoice.Total,
                PaidAmount = paidAmount,
                RefundAmount = refundAmount,
                Flags = invoice.Flags,
                IssuedOn = invoice.IssuedOn
            }).Result;
            if(invoice.PaidAmount < invoice.Total)
                return;
            uow.Execute(new CompleteInvoice
            {
                EntityID = invoice.EntityID
            });
        }

        public void OnInvoiceStatusChanged(IUnitOfWork uow, InvoiceEntity invoice)
        {
            if(!invoice.Flags.HasFlag(InvoiceFlags.Paid))
                return;
            if(invoice.Order.Flags.HasFlag(OrderFlags.Confirmed))
            {
                uow.Execute(new ProcessOrder
                {
                    EntityID = invoice.Order.EntityID
                });
            }
            else if(invoice.Order.Flags.HasFlag(OrderFlags.Processing))
            {
                uow.Execute(new CompleteOrder
                {
                    EntityID = invoice.Order.EntityID
                });
            }
        }
    }

    [Dependency("32411d4b-baad-406f-b13a-98dc69ba24ef",
        Name = "Credit Sales Model")]
    public sealed class CreditSalesModel : ISalesModel
    {
        #region Methods

        public bool CanSale(SalesItemEntity item)
        {
            return item.Attribute(Par.In("Plugin").Of(this).Name("Enabled")).ParseAs(true) &&
                item.Seller.Account.Attribute(Par.In("Plugin").Of(this).Name("Enabled")).ParseAs(true);
        }

        public decimal CalcHandlingFee(OrderEntity order)
        {
            var rate = order.Seller.Account.Attribute(Par
                .In("Plugin").Of(this).Name("Rate"))
                .ParseAs(Decimal.Zero);
            var fee = Decimal.Zero;
            if(rate != Decimal.Zero)
            {
                var period = order.Seller.Account.Attribute(Par
                    .In("Plugin").Of(this).Name("Period"))
                    .ParseAs(1);
                var repayment = CalcRepayment((double)order.Total, (double)rate, period);
                fee = (decimal)(repayment * period) - order.Total;
            }
            return fee;
        }

        public void OnOrderStatusChanged(IUnitOfWork uow, OrderEntity order)
        {
            if(order.Flags.HasFlag(OrderFlags.Confirmed))
            {
                //init shipping
                string shipComment = String.Empty;
                IDictionary<String, String> shipAttributes = null;
                using(var ioc = uow.Infrastructure.DependencyResolver())
                {
                    shipAttributes = (ioc.Resolve(order.ShippingMethodID) as IShippingMethod)
                        .InitShipping(order, out shipComment);
                }
                if(!String.IsNullOrWhiteSpace(shipComment))
                {
                    uow.Execute(new CreateOrderComment
                    {
                        HolderID = order.EntityID,
                        Text = shipComment,
                        Flags = OrderCommentFlags.Active | OrderCommentFlags.Public
                    });
                }
                if(shipAttributes != null && shipAttributes.Any())
                {
                    uow.Execute(new BulkUpdateOrderAttributes
                    {
                        HolderID = order.EntityID,
                        Attributes = shipAttributes
                    });
                    uow.Commit();
                }
                //issue invoices
                var period = order.Seller.Account.Attribute(Par
                    .In("Plugin").Of(this).Name("Period"))
                    .ParseAs(1);
                var payment = order.Total / period;
                var dtNow = DateTime.UtcNow;
                for(int i = 0; i < period; i++)
                {
                    uow.Execute(new PlaceInvoice
                    {
                        OrderID = order.EntityID,
                        Total = payment,
                        IssuedOn = dtNow.AddMonths(i + 1)
                    });
                }
            }
            else if(order.Flags.HasFlag(OrderFlags.Canceled))
            {
                //cancel shipping
                string shipComment = String.Empty;
                IDictionary<String, String> shipAttributes = null;
                using(var ioc = uow.Infrastructure.DependencyResolver())
                {
                    shipAttributes = (ioc.Resolve(order.ShippingMethodID) as IShippingMethod)
                        .CancelShipping(order, out shipComment);
                }
                if(!String.IsNullOrWhiteSpace(shipComment))
                {
                    uow.Execute(new CreateOrderComment
                    {
                        HolderID = order.EntityID,
                        Text = shipComment,
                        Flags = OrderCommentFlags.Active | OrderCommentFlags.Public
                    });
                }
                if(shipAttributes != null && shipAttributes.Any())
                {
                    uow.Execute(new BulkUpdateOrderAttributes
                    {
                        HolderID = order.EntityID,
                        Attributes = shipAttributes
                    });
                    uow.Commit();
                }
                //cancel invoices
                foreach(var i in order.Invoices
                    .Where(x =>
                        x.Flags.HasFlag(InvoiceFlags.Pending) ||
                        x.Flags.HasFlag(InvoiceFlags.Issued))
                    .ToList())
                {
                    uow.Execute(new CancelInvoice
                    {
                        EntityID = i.EntityID
                    });
                }
            }
        }

        public void OnPaymentStatusChanged(IUnitOfWork uow, PaymentEntity payment)
        {
            if(!payment.Flags.HasFlag(PaymentFlags.Refunded) &&
                !payment.Flags.HasFlag(PaymentFlags.Captured))
            {
                return;
            }

            var invoice = payment.Invoice;
            decimal paidAmount = invoice.PaidAmount;
            decimal refundAmount = invoice.RefundAmount;
            if(payment.Flags.HasFlag(PaymentFlags.Refunded))
                refundAmount += payment.Amount;
            else
                paidAmount += payment.Amount;

            invoice = uow.Execute(new UpdateInvoice
            {
                EntityID = invoice.EntityID,
                Total = invoice.Total,
                PaidAmount = paidAmount,
                RefundAmount = refundAmount,
                Flags = invoice.Flags,
                IssuedOn = invoice.IssuedOn
            }).Result;
            if(invoice.PaidAmount < invoice.Total)
                return;
            uow.Execute(new CompleteInvoice
            {
                EntityID = invoice.EntityID
            });
        }

        public void OnInvoiceStatusChanged(IUnitOfWork uow, InvoiceEntity invoice)
        {
            if(!invoice.Flags.HasFlag(InvoiceFlags.Paid))
                return;
            var loan = invoice.Order.Total;
            var paid = invoice.Order.Invoices
                .Where(x => x.Flags.HasFlag(InvoiceFlags.Paid))
                .Sum(x => x.Total);
            if(loan - paid > Decimal.Zero)
                return;
            uow.Execute(new CompleteOrder
            {
                EntityID = invoice.Order.EntityID
            });
        }

        #endregion

        #region Utils

        private static double CalcRepayment(double loan, double rate, int period)
        {
            var d = 1 + rate / 1200;
            var k = Math.Pow(d, period) * (d - 1) / (Math.Pow(d, period) - 1);
            return loan * k;
        }

        #endregion
    }
}
